package org.dandsoft.blackbox.knowledge;

import java.io.*;

/**
 * User: dvilyuzhanin
 * Date: 13.01.2009 13:51:18
 */
public class BookBuilder {

    private final static String PATH_SEPARATOR = "/";
    private final static String TYPE_SEPARATOR = ".";
    public final static String DATA_FILE = ".data";
    public final static String ENTITY_PREFIX = "entity";
    public final static String ENTITY_SUFIX = ".entity";


    private File bookFile;
    private File workDir;
    private File bookDir;
    private boolean isOpened;
    private static final int BUFFER_SIZE = 512;


    public BookBuilder(File bookFile) throws BookBuilderException {

        File tempWorkDir = new File(System.getProperty("java.io.tmpdir") + "/bbx_" + Double.doubleToLongBits(Math.random()));
        //tempWorkDir.deleteOnExit();
        init(bookFile, tempWorkDir);

    }

    public BookBuilder(File bookFile, File workDir) throws BookBuilderException {
        init(bookFile, workDir);
    }

    private void init(File bookFile, File workDir) throws BookBuilderException {
        if (bookFile == null) {
            throw new NullPointerException("File of knowledge can not be null.");
        }
        if (workDir == null) {
            throw new NullPointerException("Work dir can not be null");
        }

        this.bookFile = bookFile;
        this.workDir = workDir;
        open();
    }

    protected synchronized void open() throws BookBuilderException {
        if (!workDir.exists()) {
            workDir.mkdir();
        }

        bookDir = new File(workDir.getAbsolutePath() + "/knowledge");
        bookDir.mkdir();
        isOpened = true;
    }

    public synchronized void putBookInfo(BookInfo bookInfo) throws BookBuilderException {
        OutputStream out = null;

        try {
            out = new BufferedOutputStream(new FileOutputStream(createDataFile(), false), BUFFER_SIZE);
            bookInfo.store(out);
            out.flush();
        } catch (IOException e) {
            throw new BookBuilderException(e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public synchronized BookInfo getBookInfo() throws BookBuilderException {
        InputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(createDataFile()), BUFFER_SIZE);
            BookInfo info = new BookInfo();
            info.load(in);
            return info;
        } catch (IOException e) {
            throw new BookBuilderException();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    synchronized void storeInfo(AbstractInfo info) throws BookBuilderException {
        OutputStream out = null;
        if (info.getName() == null) {
            throw new NullPointerException("Name of info can not be null");
        }
        try {
            out = new BufferedOutputStream(
                    new FileOutputStream(createInfoFile(info.getName(), info.getGroupName(), info.getType(),
                                                        info.getFormat(), true), false), BUFFER_SIZE);
            info.store(out);
            out.flush();
        } catch (IOException e) {
            throw new BookBuilderException(e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    synchronized <Type extends AbstractInfo> Type restoreInfo(Class<Type> clazz, Type info, DataFormat format) throws BookBuilderException {
        InputStream in = null;
        try {
            File file = createInfoFile(info.getName(), info.getGroupName(), info.getType(), format, false);
            in = new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE);
            info.load(in);
            return info;
        } catch (IOException e) {
            throw new BookBuilderException(e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    synchronized boolean isInfo(InfoName name, String groupName, String type, DataFormat format) throws BookBuilderException {
        File file = createInfoFile(name, groupName, type, format, false);
        if (file != null) {
            return file.exists();
        } else {
            return false;
        }
    }

    public synchronized void storeEntityInfo(EntityInfo info) throws BookBuilderException {
        storeInfo(info);
    }


    public synchronized EntityInfo getEntityInfo(InfoName name) throws BookBuilderException {
        EntityInfo entityInfo = new EntityInfo();
        entityInfo.setName(name);
        return restoreInfo(EntityInfo.class, entityInfo, EntityInfo.FORMAT);
    }

    public synchronized void storeImageInfo(ImageInfo info) throws BookBuilderException {
        storeInfo(info);
    }

    public synchronized ImageInfo getImageInfo(InfoName name) throws BookBuilderException {
        ImageInfo imageInfo = new ImageInfo();
        imageInfo.setName(name);
        return restoreInfo(ImageInfo.class, imageInfo, EntityInfo.FORMAT);
    }

    public synchronized boolean isImageInfo(InfoName name) throws BookBuilderException {
        return isInfo(name, ImageInfo.IMAGE_GROUP_NAME, ImageInfo.TYPE, DataFormat.Xml);
    }

    public synchronized void storeImageContentInfo(ImageContentInfo content) throws BookBuilderException {
        if (isImageInfo(content.getName())) {
            storeInfo(content);
        } else {
            throw new InfoNotFoundException(content.getName());
        }
    }

    public synchronized ImageContentInfo getImageContentInfo(InfoName name) throws BookBuilderException {
        ImageContentInfo info = new ImageContentInfo();
        info.setName(name);
        return restoreInfo(ImageContentInfo.class, info, ImageContentInfo.FORMAT);
    }

    private File createInfoFile(InfoName name, String groupName, String type, DataFormat format, boolean isCreate) {
        String[] folders = name.getPackageNameComponents();

        File groupFolder = createGroupFolder(groupName);
        if (!groupFolder.exists()) {
            if (isCreate) {
                groupFolder.mkdir();
            } else {
                return null;
            }
        }
        File packageFolder = createInfoPackageFolder(groupFolder, -1, folders, isCreate);
        if (!packageFolder.exists()) {
            if (isCreate) {
                packageFolder.mkdir();
            } else {
                return null;
            }
        }

        return new File(packageFolder.getAbsolutePath() + PATH_SEPARATOR + name.getName() + TYPE_SEPARATOR +
                        type + "." + format.getAlias());
    }

    private File createInfoPackageFolder(File currentFolder, int currentLevel, String[] folders, boolean isCreate) {
        if (folders.length == 0) {
            return currentFolder;
        } else {
            File nextFolder = new File(currentFolder.getAbsolutePath() + PATH_SEPARATOR + folders[currentLevel + 1]);
            if (!nextFolder.exists()) {
                if (isCreate) {
                    nextFolder.mkdir();
                } else {
                    return null;
                }
            }

            if (currentLevel + 1 < folders.length - 1) {
                return createInfoPackageFolder(nextFolder, currentLevel + 1, folders, isCreate);
            } else {
                return nextFolder;
            }
        }
    }


    protected static String createPath(String packageName) {
        StringBuilder builder = new StringBuilder(packageName.length());
        for (int i = 0; i < packageName.length(); i++) {
            char ch = packageName.charAt(i);
            if (ch == '.') {
                builder.append('/');
            } else {
                builder.append(ch);
            }
        }
        return builder.toString();
    }


    private File createDataFile() {
        return new File(bookDir.getAbsolutePath() + PATH_SEPARATOR + DATA_FILE);
    }

    private File createGroupFolder(String groupName) {
        return new File(bookDir.getAbsolutePath() + PATH_SEPARATOR + groupName);
    }

    public boolean isOpened() {
        return isOpened;
    }


}
